package mvc

import (
	"web"
	"reflect"
	"container/vector"
	"strings"
	"fmt"
	"os"
	"http"
	"runtime/debug"
	"strconv"
)

const (
	_ = iota
	REDIRECT
	RENDER
	VIEW
	ERROR
)

const (
	CHARA = 65
	CHARZ = 90
)

type Mvc struct {
	controllers       map[string]*controllerInfo
	defaultController string
	defaultAction     string
}

type Controller interface {
	Init()
}

type ActionResult struct {
	//REDIRECT, RENDER, VIEW, or ERROR
	Type uint32
	Data interface{}
}

type controllerInfo struct {
	methods    map[string]*methodInfo
	initMethod *methodInfo
	typ        *reflect.PtrType
	name       string
}

type methodInfo struct {
	funcValue        *reflect.FuncValue
	structParameters []*reflect.PtrType
	otherParameters  []reflect.Type
}

type httpError struct {
	code    int
	message string
}

func (this *httpError) String() string {
	return fmt.Sprintf("Error Code : %s. Message : %s.", this.code, this.message)
}

func NewMvc() *Mvc {
	m := new(Mvc)
	m.controllers = make(map[string]*controllerInfo)
	return m
}

func newController(val interface{}) *controllerInfo {
	rv := reflect.NewValue(val)
	typ := rv.Type().(*reflect.PtrType)
	ci := new(controllerInfo)
	ci.typ = typ
	ci.methods = make(map[string]*methodInfo)
	expectedReturnType := reflect.Typeof((*ActionResult)(nil))
	for i := 0; i < typ.NumMethod(); i++ {
		mn := typ.Method(i)

		//Actions must be public Methods of a struct
		firstChar := []byte(mn.Name)[0]
		if firstChar < CHARA || firstChar > CHARZ {
			continue
		}

		actionName := strings.ToLower(mn.Name)
		//Is Init
		if actionName == "init" {
			ci.initMethod = new(methodInfo)
			ci.initMethod.funcValue = mn.Func
			ci.initMethod.structParameters = nil
			continue
		}

		//Check if the return type is *ActionResult
		if mn.Type.NumOut() != 1 {
			continue
		} else if mn.Type.Out(0) != expectedReturnType {
			continue
		}

		ci.methods[actionName] = new(methodInfo)
		ci.methods[actionName].funcValue = mn.Func
		ci.methods[actionName].structParameters, ci.methods[actionName].otherParameters = buildInParameterInfo(mn.Type)
	}

	return ci
}

func buildInParameterInfo(f *reflect.FuncType) ([]*reflect.PtrType, []reflect.Type) {
	numIn := f.NumIn()
	if numIn == 0 {
		return nil, nil
	}

	var modelParams vector.Vector
	var otherParams vector.Vector

	i := 1
	for ; i < numIn; i++ {
		p := f.In(i)
		if v, isPtr := p.(*reflect.PtrType); !isPtr {
			break
		} else {
			if _, isStruct := p.(*reflect.PtrType).Elem().(*reflect.StructType); !isStruct {
				break
			} else {
				modelParams.Push(v)
			}
		}
	}

	for ; i < numIn; i++ {
		otherParams.Push(f.In(i))
	}

	var mTypes []*reflect.PtrType
	var oTypes []reflect.Type
	if modelParams.Len() == 0 {
		mTypes = nil
	} else {
		mTypes = make([]*reflect.PtrType, modelParams.Len())
		for i := 0; i < modelParams.Len(); i++ {
			mTypes[i] = modelParams.At(i).(*reflect.PtrType)
		}
	}

	if otherParams.Len() == 0 {
		oTypes = nil
	} else {
		oTypes = make([]reflect.Type, otherParams.Len())
		for i := 0; i < otherParams.Len(); i++ {
			oTypes[i] = otherParams.At(i).(reflect.Type)
		}
	}

	return mTypes, oTypes
}

func (this *Mvc) RegisterController(val Controller) {
	rv := reflect.NewValue(val)
	tn := rv.Type().String()
	temp := strings.Split(tn, `.`, -1)
	tn = temp[len(temp)-1]
	ci := newController(val)
	ci.name = strings.ToLower(tn)
	this.controllers[strings.ToLower(tn)] = ci
}

//Set default controller and action
func (this *Mvc) SetDefault(defaultController string, defaultAction string) {
	this.defaultController = strings.ToLower(defaultController)
	this.defaultAction = strings.ToLower(defaultAction)
}

func call(ctx *web.Context, ci *controllerInfo, action string, params []string) *ActionResult {
	//create a new controller and call its Init method

	cval := reflect.MakeZero(ci.typ).(*reflect.PtrValue)
	cval.PointTo(reflect.MakeZero(ci.typ.Elem()))
	initParams := make([]reflect.Value, 1)
	initParams[0] = cval
	ci.initMethod.funcValue.Call(initParams)

	//call the method which should response the action

	var args vector.Vector //the controller and arguments whose type is a data Model

	args.Push(cval)
	for _, v := range ci.methods[action].structParameters {
		s := reflect.MakeZero(v.Elem())
		pv := reflect.MakeZero(v).(*reflect.PtrValue)
		pv.PointTo(s)
		args.Push(pv)
		ctx.UnmarshalParams(pv.Interface())
	}

	numArg := args.Len() + len(params)
	callArgs := make([]reflect.Value, numArg)

	numCallerAndModelArg := args.Len()
	for i := 0; i < numCallerAndModelArg; i++ {
		//model parameters
		callArgs[i] = args.At(i).(*reflect.PtrValue)
	}

	//other parameters
	errFmt := `%s is not a valid value of type %s`
	for i := 0; i < len(params); i++ {
		v := params[i]
		t := ci.methods[action].otherParameters[i]
		index := numCallerAndModelArg + i
		//string, int, uint, etc 
		switch t.(type) {
		case *reflect.BoolType:
			bv, err := strconv.Atob(v)
			if err != nil {
				panic(fmt.Sprintf(errFmt, v, "bool"))
			}
			callArgs[index] = reflect.NewValue(bv)
		case *reflect.StringType:
			callArgs[index] = reflect.NewValue(v)
		case *reflect.IntType:
			iv, err := strconv.Atoi(v)
			if err != nil {
				panic(fmt.Sprintf(errFmt, v, "int"))
			}
			callArgs[index] = reflect.NewValue(iv)
		case *reflect.UintType:
			uiv, err := strconv.Atoui(v)
			if err != nil {
				panic(fmt.Sprintf(errFmt, v, "uint"))
			}
			callArgs[index] = reflect.NewValue(uiv)
		default:
			panic(fmt.Sprintf("Unknow parameter type : %T", t))
		}
	}

	r := ci.methods[action].funcValue.Call(callArgs)[0].Interface().(*ActionResult)
	return r
}

func (this *Mvc) Handler(ctx *web.Context, val string) {
	defer func() {
		if err := recover(); err != nil {
			ctx.Logger.Println("Error occured: ", err)
			debug.PrintStack()
		}
	}()

	ci, action, params, err := this.parseUrl(val, ctx.Request.Method)
	if err != nil {
		e := err.(*httpError)
		writeResponse(ctx, e.code, e.message)
		return
	}

	r := call(ctx, ci, action, *params)

	if r.Type == VIEW {
		ctx.WriteString(view(r.Data, ci.name, action))
	} else if r.Type == RENDER {
		ctx.WriteString(r.Data.(string))
	} else if r.Type == REDIRECT {
		ctx.Redirect(302, r.Data.(string))
	} else {
		writeResponse(ctx, 503, r.Data.(string))
	}
}

func writeResponse(ctx *web.Context, code int, message string) {
	ctx.StartResponse(code)
	ctx.WriteString(message)
}

func (this *Mvc) parseUrl(val string, method string) (ci *controllerInfo, action string, params *[]string, err os.Error) {
	e := new(httpError)
	if strings.HasPrefix(val, `/`) {
		val = val[1:]
	}

	parts := strings.Split(val, "/", -1)
	var controller string

	if params == nil {
		params = new([]string)
	}

	if val == "" || len(parts) == 0 {
		controller = this.defaultController
		action = this.defaultAction
	} else {
		controller = strings.ToLower(parts[0])

		if len(parts) > 1 {
			action = strings.ToLower(parts[1])
			if len(parts) > 2 {
				*params = parts[2:]
			}
		} else {
			action = this.defaultAction
		}
	}

	method = strings.ToLower(method)
	if strings.ToLower(method) != "get" {
		action = action + strings.ToLower(method)
	}

	ci, ok := this.controllers[controller]

	if !ok {
		e.code = 404
		e.message = fmt.Sprintf("Page Not Found. Please check the url: %s", val)
		err = e
		return
	} else if _, ok := ci.methods[action]; !ok {
		e.code = 404
		e.message = fmt.Sprintf("Page Not Found. Please check the url: %s", val)
		err = e
		return
	}

	err = nil
	return
}

func RedirectToAction(controller string, action string, param ...string) *ActionResult {
	r := new(ActionResult)
	r.Type = REDIRECT
	content := URLSEP + controller + URLSEP + action
	for _, p := range param {
		content = content + URLSEP + http.URLEscape(p)
	}
	r.Data = content
	return r
}

func RedirectToUrl(url string) *ActionResult {
	if !strings.HasPrefix(url, URLSEP) {
		url = URLSEP + url
	}
	return &ActionResult{REDIRECT, url}
}

func Render(content string) *ActionResult {
	return &ActionResult{RENDER, content}
}

func View(content interface{}) *ActionResult {
	return &ActionResult{VIEW, content}
}

func Error(content string) *ActionResult {
	return &ActionResult{ERROR, content}
}
